/*
 * Copyright (c) 2025 [ByteDance Ltd. and/or its affiliates.] and/or its affiliates
 * SPDX-License-Identifier: LGPL v2.1+
*/
#include <windows.h>

#define COBJMACROS

#include <initguid.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <d3dcompiler.h>

#include <stdio.h>
#include <string.h>

#include "libavutil/avstring.h"
#include "libavutil/common.h"
#include "libavutil/eval.h"
#include "libavutil/hwcontext.h"
#include "libavutil/hwcontext_d3d11va.h"
#include "libavutil/internal.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
#include "libavutil/pixdesc.h"
#include "libavutil/time.h"

#include "avfilter.h"
#include "formats.h"
#include "internal.h"
#include "video.h"

#pragma comment(lib, "d3dcompiler.lib")

static const char* const globVertexShader =\
"\
struct VS_INPUT\
{\
    float4 Pos : SV_POSITION;\
    float2 Tex : TEXCOORD;\
};\
VS_INPUT main(uint index : SV_VERTEXID)\
{\
    VS_INPUT output = (VS_INPUT)0;\
    float2 tex = float2(index & 1, index >> 1);\
    output.Pos = float4((tex.x - 0.5f) * 2.0f, -(tex.y - 0.5f) * 2.0f, 0.0f, 1.0f);\
    output.Tex = tex;\
    return output;\
}\
";

#define DECLEAR_PARAM \
"\
cbuffer PS_CONSTANT_BUFFER : register(b0)\
{\
int   method; \
float bicubic_param; \
float dx; \
float dy; \
float dst_width;\
float dst_height;\
float src_width;\
float src_height;\
float x_scale;\
float y_scale;\
float x_stride;\
float y_stride;\
};\
\
Texture2D txInput : register(t0);\
\
SamplerState samplerState : register(s0);\
\
struct PS_INPUT\
{\
    float4 Pos : SV_POSITION;\
    float2 Tex : TEXCOORD;\
};"

#define BICUBIC_COEFF    \
"float4 bicubic_coeffs(float x)\
{\
    float4 coeffs;\
    coeffs.x = ((bicubic_param * (x + 1) - 5 * bicubic_param) * (x + 1) + 8 * bicubic_param) * (x + 1) - 4 * bicubic_param;\
    coeffs.y = ((bicubic_param + 2) * x - (bicubic_param + 3)) * x * x + 1;\
    coeffs.z = ((bicubic_param + 2) * (1 - x) - (bicubic_param + 3)) * (1 - x) * (1 - x) + 1;\
    coeffs.w = 1.0f - coeffs.x - coeffs.y - coeffs.z;\
    return coeffs;\
}"

#define APPLY_COEFF \
" (v0 * coeffs.x + v1 * coeffs.y + v2 * coeffs.z + v3 * coeffs.w);" 

#define APPLY_COEFF_LUMA \
"float process_coeffs(float4 coeffs, float v0, float v1, float v2, float v3) { float v = " APPLY_COEFF " return v;}"

#define APPLY_COEFF_CHROMA \
"float2 process_coeffs(float4 coeffs, float2 v0, float2 v1, float2 v2, float2 v3) { float2 v = " APPLY_COEFF " return v;}"

#define LUMA_PIXEL \
"float pixel(float x, float y)\
{\
    float2 tex = float2(x * x_stride, y * y_stride); \
    return txInput.Sample(samplerState, tex).x; \
}" 

#define CHROMA_PIXEL  \
"float2 pixel(float x, float y)\
{\
    float2 tex = float2(x * x_stride, y * y_stride); \
    return txInput.Sample(samplerState, tex).xy; \
}" 

#define BICUBIC_SCALE \
"bicubic_scale(float2 tex) {\
    float x = (tex.x * dst_width  + 0.5f) * x_scale - 0.5f;\
    float y = (tex.y * dst_height + 0.5f) * y_scale - 0.5f;\
    float xi = floor(x);\
    float yi = floor(y);\
    float4 coeffsx = bicubic_coeffs(x - xi);\
    float4 coeffsy = bicubic_coeffs(y - yi);\
                                            \
    return process_coeffs(coeffsy,\
        process_coeffs(coeffsx, pixel(xi - 1, yi - 1), pixel(xi, yi - 1), pixel(xi + 1, yi - 1), pixel(xi + 2, yi - 1)),\
        process_coeffs(coeffsx, pixel(xi - 1, yi),     pixel(xi, yi),     pixel(xi + 1, yi),     pixel(xi + 2, yi)),\
        process_coeffs(coeffsx, pixel(xi - 1, yi + 1), pixel(xi, yi + 1), pixel(xi + 1, yi + 1), pixel(xi + 2, yi + 1)),\
        process_coeffs(coeffsx, pixel(xi - 1, yi + 2), pixel(xi, yi + 2), pixel(xi + 1, yi + 2), pixel(xi + 2, yi + 2))\
    );\
}"

#define BICUBIC_SCALE_LUMA  \
"float " BICUBIC_SCALE 

#define BICUBIC_SCALE_CHROMA  \
"float2 " BICUBIC_SCALE 

#define BILINEAR_SCALE \
"bilinear_scale(float2 tex) {\
        float x = (tex.x * dst_width  + 0.5f) * x_scale; \
        float y = (tex.y * dst_height + 0.5f) * y_scale; \
        return (pixel(x - dx, y - dy) + pixel(x + dx, y - dy) + pixel(x - dx, y + dy) + pixel(x + dx, y + dy)) / 4; \
}"

#define BILINEAR_SCALE_LUMA \
"float " BILINEAR_SCALE 

#define BILINEAR_SCALE_CHROMA \
"float2 " BILINEAR_SCALE

#define SCALE \
"main(PS_INPUT input) : SV_Target\
{\
    if (method == 1) {\
        return bicubic_scale(input.Tex);\
    }\
    return bilinear_scale(input.Tex);\
}"

#define SCALE_LUMA \
BILINEAR_SCALE_LUMA \
BICUBIC_SCALE_LUMA  \
"float " SCALE 

#define SCALE_CHROMA \
BILINEAR_SCALE_CHROMA \
BICUBIC_SCALE_CHROMA  \
"float2 " SCALE 

static const char* const globPixelLumaShader = \
DECLEAR_PARAM \
BICUBIC_COEFF \
APPLY_COEFF_LUMA \
LUMA_PIXEL  \
SCALE_LUMA
;

static const char* const globPixelChromaShader = \
DECLEAR_PARAM \
BICUBIC_COEFF \
APPLY_COEFF_CHROMA \
CHROMA_PIXEL \
SCALE_CHROMA
;

static const char *const var_names[] = {
    "PI",
    "PHI",
    "E",
    "in_w",   "iw",
    "in_h",   "ih",
    "out_w",  "ow",
    "out_h",  "oh",
    "a", "dar",
    "sar",
    NULL
};

enum var_name {
    VAR_PI,
    VAR_PHI,
    VAR_E,
    VAR_IN_W,   VAR_IW,
    VAR_IN_H,   VAR_IH,
    VAR_OUT_W,  VAR_OW,
    VAR_OUT_H,  VAR_OH,
    VAR_A, VAR_DAR,
    VAR_SAR,
    VARS_NB
};

enum DownsamplingAlgorithm {
    BILINEAR,
    BICUBIC
};

typedef struct PS_CONSTANT_BUFFER {
    int   method;
    float bicubic_param;
    float dx;
    float dy;

    float dst_width;
    float dst_height;
    float src_width;
    float src_height;

    float x_scale;
    float y_scale;
    float x_stride;
    float y_stride;
} PS_CONSTANT_BUFFER;

typedef struct D3D11ScaleContext {
    const AVClass *class;

    ID3D11Device        *device;
    ID3D11Query         *query;
    ID3D11VideoDevice   *video_device;
    ID3D11VideoContext  *video_ctx;
    ID3D11DeviceContext *device_ctx;

    ID3D11Texture2D     *texture;
    ID3D11Buffer        *luma_cb_buffer;
    ID3D11Buffer        *chroma_cb_buffer;
    ID3D11VertexShader  *vertex_shader;
    ID3D11PixelShader   *luma_pixel_shader;
    ID3D11PixelShader   *chroma_pixel_shader;
    ID3D11SamplerState  *sampler_linear;

    /**
     * New dimensions. Special values are:
     *   0 = original width/height
     *  -1 = keep original aspect
     */
    int w, h;

    /**
     * Output sw format. AV_PIX_FMT_NONE for no conversion.
     */
    enum AVPixelFormat format;
    enum DownsamplingAlgorithm method;

    char *w_expr;               ///< width  expression string
    char *h_expr;               ///< height expression string
    char *format_str;
    char* method_str;
} D3D11ScaleContext;

#define D3D11_RELEASE(name, ctx)    if(ctx) {ID3D11##name##_Release(ctx); ctx = NULL;}
#define D3D11_CALL(func, ctx)       ctx->lpVtbl->##func(ctx)
#define D3D11_CALLEX(func, ctx)     if(ctx) {D3D11_CALL(func, ctx);}

static av_cold int d3d11scale_init(AVFilterContext *ctx)
{
    D3D11ScaleContext*s = ctx->priv;

    if (!strcmp(s->format_str, "same")) {
        s->format = AV_PIX_FMT_NONE;
    } else {
        s->format = av_get_pix_fmt(s->format_str);
        if (s->format == AV_PIX_FMT_NONE) {
            av_log(ctx, AV_LOG_ERROR, "Unrecognized pixel format: %s\n", s->format_str);
            return AVERROR(EINVAL);
        }
    }

    return 0;
}

static av_cold void d3d11scale_uninit(AVFilterContext *ctx)
{
    D3D11ScaleContext*s = ctx->priv;

    D3D11_RELEASE(Device,           s->device)
    D3D11_RELEASE(Query,            s->query)
    D3D11_RELEASE(VideoContext,     s->video_ctx)
    D3D11_RELEASE(VideoDevice,      s->video_device)
    D3D11_RELEASE(DeviceContext,    s->device_ctx)
    D3D11_RELEASE(Buffer,           s->luma_cb_buffer)
    D3D11_RELEASE(Buffer,           s->chroma_cb_buffer)
    D3D11_RELEASE(Texture2D,        s->texture)
    D3D11_RELEASE(VertexShader,     s->vertex_shader)
    D3D11_RELEASE(PixelShader,      s->luma_pixel_shader)
    D3D11_RELEASE(PixelShader,      s->chroma_pixel_shader)
    D3D11_RELEASE(SamplerState,     s->sampler_linear)
}

static int init_out_pool(AVFilterContext *ctx,
                         int out_width, int out_height)
{
    D3D11ScaleContext *s = ctx->priv;
    AVFilterLink *outlink = ctx->outputs[0];

    AVHWFramesContext *in_frames_ctx;
    AVHWFramesContext *out_frames_ctx;
    AVD3D11VAFramesContext *in_frames_hwctx;
    AVD3D11VAFramesContext *out_frames_hwctx;
    enum AVPixelFormat in_format;
    enum AVPixelFormat out_format;

    /* check that we have a hw context */
    if (!ctx->inputs[0]->hw_frames_ctx) {
        av_log(ctx, AV_LOG_ERROR, "No hw context provided on input\n");
        return AVERROR(EINVAL);
    }
    in_frames_ctx   = (AVHWFramesContext*)ctx->inputs[0]->hw_frames_ctx->data;
    in_frames_hwctx = in_frames_ctx->hwctx;

    in_format     = in_frames_ctx->sw_format;
    out_format    = (s->format == AV_PIX_FMT_NONE) ? in_format : s->format;

    outlink->hw_frames_ctx = av_hwframe_ctx_alloc(in_frames_ctx->device_ref);
    if (!outlink->hw_frames_ctx)
        return AVERROR(ENOMEM);
    out_frames_ctx   = (AVHWFramesContext*)outlink->hw_frames_ctx->data;
    out_frames_hwctx = out_frames_ctx->hwctx;

    out_frames_ctx->format            = AV_PIX_FMT_D3D11;
    out_frames_ctx->width             = FFALIGN(out_width,  32);
    out_frames_ctx->height            = FFALIGN(out_height, 32);
    out_frames_ctx->sw_format         = out_format;
    out_frames_ctx->initial_pool_size = 8;

    out_frames_hwctx->use_multi_textures = 1;
    out_frames_hwctx->BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;

    int ret = ff_filter_init_hw_frames(ctx, outlink, 8);
    if (ret < 0)
        return ret;

    ret = av_hwframe_ctx_init(outlink->hw_frames_ctx);
    if (ret < 0)
        return ret;

    return 0;
}

static int init_d3d11_hwcontext(AVFilterContext* ctx) {
    D3D11ScaleContext* s = ctx->priv;
    AVHWFramesContext* in_frames_ctx = (AVHWFramesContext*)ctx->inputs[0]->hw_frames_ctx->data;
    AVD3D11VAFramesContext* in_frames_hwctx = in_frames_ctx->hwctx;
    AVD3D11VADeviceContext* device_hwctx = in_frames_ctx->device_ctx->hwctx;

    s->device = device_hwctx->device;
    ID3D11Device_AddRef(s->device);

    s->video_ctx = device_hwctx->video_context;
    ID3D11VideoContext_AddRef(s->video_ctx);

    s->video_device = device_hwctx->video_device;
    ID3D11VideoDevice_AddRef(s->video_device);

    s->device_ctx = device_hwctx->device_context;
    ID3D11DeviceContext_AddRef(s->device_ctx);

    D3D11_QUERY_DESC desc_query;
    desc_query.Query = D3D11_QUERY_EVENT;
    desc_query.MiscFlags = 0;
    ID3D11Device_CreateQuery(s->device, &desc_query, &s->query);
    if (s->query) {
        ID3D11DeviceContext_End(s->device_ctx, s->query);
    }

    return 0;
}

static void d3d11scale_query_finish(AVFilterContext* ctx) {
    D3D11ScaleContext* s = ctx->priv;
    if (s->query) {
        ID3D11DeviceContext_End(s->device_ctx, s->query);
        UINT flags = 0;
        HRESULT hr = S_OK;
        unsigned int attempt = 0;
        while (S_FALSE == (hr = ID3D11DeviceContext_GetData(s->device_ctx, s->query, NULL, 0, flags))) {
            hr = ID3D11Device_GetDeviceRemovedReason(s->device);
            if (FAILED(hr)) {
                av_log(NULL, AV_LOG_ERROR, "d3d11 device lost");
                return;
            }
            if (attempt++ > 100) {
                flags = D3D11_ASYNC_GETDATA_DONOTFLUSH;
                attempt = 0;
            }
            else {
                flags = 0;
            }
            Sleep(0);
        }
    }
}

static int create_vertex_shader(ID3D11Device *device, const char* source, ID3D11VertexShader **shader) {
    unsigned int flags = 0;
#if defined(_DEBUG) || defined(DEBUG)
    flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
    int ret = 0;

    ID3DBlob *blob = NULL, *msg = NULL;
    HRESULT hr = D3DCompile(source, strlen(source), NULL, NULL, NULL, "main", "vs_4_0", flags, 0, &blob, &msg);
    if (FAILED(hr)) {
        av_log(NULL, AV_LOG_ERROR, "failed to compile vertex shader error: %s\n", (char *)D3D11_CALL(GetBufferPointer, msg));
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    hr = ID3D11Device_CreateVertexShader(device, D3D11_CALL(GetBufferPointer, blob), D3D11_CALL(GetBufferSize, blob), NULL, shader);
    if (FAILED(hr)) {
        av_log(NULL, AV_LOG_ERROR, "failed to create vertex shader error: 0x%x", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }
fail:
    D3D11_CALLEX(Release, blob);
    D3D11_CALLEX(Release, msg);
    return ret;
}

static int create_pixel_shader(ID3D11Device* device, const char* source, ID3D11PixelShader** shader) {
    unsigned int flags = 0;
#if defined(_DEBUG) || defined(DEBUG)
    flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
    int ret = 0;
    ID3DBlob* blob = NULL, * msg = NULL;
    HRESULT hr = D3DCompile(source, strlen(source), NULL, NULL, NULL, "main", "ps_4_0", flags, 0, &blob, &msg);
    if (FAILED(hr)) {
        av_log(NULL, AV_LOG_ERROR, "failed to compile pixel shader error: %s\n", (char*)D3D11_CALL(GetBufferPointer, msg));
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    hr = ID3D11Device_CreatePixelShader(device, D3D11_CALL(GetBufferPointer, blob), D3D11_CALL(GetBufferSize, blob), NULL, shader);
    if (FAILED(hr)) {
        av_log(NULL, AV_LOG_ERROR, "failed to create pixel shader error: 0x%x", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

fail:
    D3D11_CALLEX(Release, blob);
    D3D11_CALLEX(Release, msg);
    return ret;
}

static int init_d3d11_shader(AVFilterContext* ctx,
    int in_width, int in_height,
    int out_width, int out_height,
    int method) {
    D3D11ScaleContext* s = ctx->priv;

    int ret = create_vertex_shader(s->device, globVertexShader, &s->vertex_shader);
    if (ret < 0) {
        return ret;
    }

    ret = create_pixel_shader(s->device, globPixelLumaShader, &s->luma_pixel_shader);
    if (ret < 0) {
        return ret;
    }

    ret = create_pixel_shader(s->device, globPixelChromaShader, &s->chroma_pixel_shader);
    if (ret < 0) {
        return ret;
    }

    D3D11_SAMPLER_DESC desc;
    memset(&desc, 0, sizeof(desc));

    desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    desc.MinLOD = 0;
    desc.MaxLOD = D3D11_FLOAT32_MAX;
    HRESULT hr = ID3D11Device_CreateSamplerState(s->device, &desc, &s->sampler_linear);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "failed to CreateSamplerState: 0x%x\n", hr);
        return AVERROR_UNKNOWN;
    }

    D3D11_BUFFER_DESC buffer_desc;
    memset(&buffer_desc, 0, sizeof(buffer_desc));
    buffer_desc.ByteWidth = sizeof(PS_CONSTANT_BUFFER);
    buffer_desc.Usage = D3D11_USAGE_IMMUTABLE;
    buffer_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    buffer_desc.CPUAccessFlags = 0;

    PS_CONSTANT_BUFFER buf;
    memset(&buf, 0, sizeof(buf));

   
    buf.src_width = in_width;
    buf.src_height = in_height;
    buf.dst_width = out_width;
    buf.dst_height = out_height;
    buf.y_scale = buf.src_height / buf.dst_height;
    buf.x_scale = buf.src_width / buf.dst_width;
    buf.y_stride = 1.0 / buf.src_height;
    buf.x_stride = 1.0 / buf.src_width;
    float wy = FFMIN(FFMAX(0.5f * (buf.y_scale - 1.0f), 0.0f), 1.0f);
    float wx = FFMIN(FFMAX(0.5f * (buf.x_scale - 1.0f), 0.0f), 1.0f);
    buf.dy = wy / (0.5f + wy);
    buf.dx = wx / (0.5f + wx);
    buf.method = method;
    buf.bicubic_param = -0.5;

    D3D11_SUBRESOURCE_DATA d = { 0 };
    d.pSysMem = &buf;
    hr = ID3D11Device_CreateBuffer(s->device, &buffer_desc, &d, &s->luma_cb_buffer);
    if (FAILED(hr) || !s->luma_cb_buffer) {
        av_log(ctx, AV_LOG_ERROR, "failed to create constant buffer error: 0x%x\n", hr);
        return AVERROR_UNKNOWN;
    }
    
    buf.src_width = in_width / 2;
    buf.src_height = in_height / 2;
    buf.dst_width = out_width / 2;
    buf.dst_height = out_height / 2;
    buf.y_scale = buf.src_height / buf.dst_height;
    buf.x_scale = buf.src_width / buf.dst_width;
    buf.y_stride = 1.0 / buf.src_height;
    buf.x_stride = 1.0 / buf.src_width;
    wy = FFMIN(FFMAX(0.5f * (buf.y_scale - 1.0f), 0.0f), 1.0f);
    wx = FFMIN(FFMAX(0.5f * (buf.x_scale - 1.0f), 0.0f), 1.0f);
    buf.dy = wy / (0.5f + wy);
    buf.dx = wx / (0.5f + wx);
    buf.method = 0;
    buf.bicubic_param = -0.5;

    hr = ID3D11Device_CreateBuffer(s->device, &buffer_desc, &d, &s->chroma_cb_buffer);
    if (FAILED(hr) || !s->chroma_cb_buffer) {
        av_log(ctx, AV_LOG_ERROR, "failed to create constant buffer error: 0x%x\n", hr);
        return AVERROR_UNKNOWN;
    }

    return 0;
}

static int d3d11scale_init_filter(AVFilterContext *ctx, int in_width, int in_height,
                              int out_width, int out_height ,int method)
{
    int ret;

    d3d11scale_uninit(ctx);

    ret = init_out_pool(ctx, out_width, out_height);
    if (ret < 0)
        return ret;

    ret = init_d3d11_hwcontext(ctx);
    if (ret < 0)
        return ret;

    ret = init_d3d11_shader(ctx, in_width, in_height, out_width, out_height, method);
    if (ret < 0)
        return ret;

    return 0;
}

static int d3d11scale_config_props(AVFilterLink *outlink)
{
    AVFilterContext *ctx = outlink->src;
    AVFilterLink *inlink = outlink->src->inputs[0];
    D3D11ScaleContext*s = ctx->priv;
    int64_t w, h;
    int method;
    double var_values[VARS_NB], res;
    char *expr;
    int ret;

    var_values[VAR_PI]    = M_PI;
    var_values[VAR_PHI]   = M_PHI;
    var_values[VAR_E]     = M_E;
    var_values[VAR_IN_W]  = var_values[VAR_IW] = inlink->w;
    var_values[VAR_IN_H]  = var_values[VAR_IH] = inlink->h;
    var_values[VAR_OUT_W] = var_values[VAR_OW] = NAN;
    var_values[VAR_OUT_H] = var_values[VAR_OH] = NAN;
    var_values[VAR_A]     = (double) inlink->w / inlink->h;
    var_values[VAR_SAR]   = inlink->sample_aspect_ratio.num ?
        (double) inlink->sample_aspect_ratio.num / inlink->sample_aspect_ratio.den : 1;
    var_values[VAR_DAR]   = var_values[VAR_A] * var_values[VAR_SAR];

    av_expr_parse_and_eval(&res, (expr = s->w_expr),
                           var_names, var_values,
                           NULL, NULL, NULL, NULL, NULL, 0, ctx);
    s->w = var_values[VAR_OUT_W] = var_values[VAR_OW] = res;
    if ((ret = av_expr_parse_and_eval(&res, (expr = s->h_expr),
                                      var_names, var_values,
                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
        goto fail;
    s->h = var_values[VAR_OUT_H] = var_values[VAR_OH] = res;

    if ((ret = av_expr_parse_and_eval(&res, (expr = s->w_expr),
                                      var_names, var_values,
                                      NULL, NULL, NULL, NULL, NULL, 0, ctx)) < 0)
        goto fail;
    s->w = res;

    w = s->w;
    h = s->h;

    if (w <  -1 || h <  -1) {
        av_log(ctx, AV_LOG_ERROR, "error, invalid d3d11 scale size\n");
        return AVERROR(EINVAL);
    }
    if (w == -1 && h == -1)
        s->w = s->h = 0;

    if (!(w = s->w))
        w = inlink->w;
    if (!(h = s->h))
        h = inlink->h;
    if (w == -1)
        w = av_rescale(h, inlink->w, inlink->h);
    if (h == -1)
        h = av_rescale(w, inlink->h, inlink->w);

    outlink->w = w;
    outlink->h = h;


    method = !strcmp(s->method_str, "bicubic");

    ret = d3d11scale_init_filter(ctx, inlink->w, inlink->h, w, h , method);
    if (ret < 0)
        return ret;

    av_log(ctx, AV_LOG_VERBOSE, "w:%d h:%d -> w:%d h:%d\n",
           inlink->w, inlink->h, outlink->w, outlink->h);


    if (inlink->sample_aspect_ratio.num)
        outlink->sample_aspect_ratio = av_mul_q((AVRational){outlink->h*inlink->w,
                                                             outlink->w*inlink->h},
                                                inlink->sample_aspect_ratio);
    else
        outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;

    return 0;

fail:
    av_log(ctx, AV_LOG_ERROR,
           "error when evaluating the expression '%s'\n", expr);
    return ret;
}

static void d3d11scale_set_view_port(AVFilterContext* ctx, int chroma) {
    D3D11ScaleContext* s = ctx->priv;

    D3D11_VIEWPORT vp = { 0 };
    vp.Width = s->w / (1 + chroma);
    vp.Height = s->h / (1 + chroma);
    vp.MaxDepth = 1;
    ID3D11DeviceContext_RSSetViewports(s->device_ctx, 1, &vp);
}

static int d3d11scale_filter_frame(AVFilterLink* link, AVFrame* in)
{
    AVFilterContext* ctx = link->dst;
    D3D11ScaleContext* s = ctx->priv;
    AVFilterLink* outlink = ctx->outputs[0];

    AVFrame* out = NULL;

    int ret = 0;
    HRESULT hr = S_OK;
    ID3D11ShaderResourceView* shader_resource_luma = NULL;
    ID3D11ShaderResourceView* shader_resource_chroma = NULL;

    ID3D11RenderTargetView* render_target_luma = NULL;
    ID3D11RenderTargetView* render_target_chroma = NULL;

    D3D11_TEXTURE2D_DESC desc;

    ID3D11Texture2D* in_tex = (ID3D11Texture2D*)in->data[0];
    UINT index = (UINT)in->data[1];
    ID3D11Texture2D_GetDesc(in_tex, &desc);
    ID3D11Texture2D* out_tex = NULL;
    const int is_nv12 = desc.Format == DXGI_FORMAT_NV12;
    int is_dst_nv12 = 1;

    if (!s->texture) {
        desc.ArraySize = 1;
        desc.MiscFlags = 0;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.Width = in->width;
        desc.Height = in->height;
        hr = ID3D11Device_CreateTexture2D(s->device, &desc, NULL, &s->texture);
        if (FAILED(hr)) {
            av_log(ctx, AV_LOG_ERROR, "failed to CreateTexture2D error: 0x%x\n", hr);
            ret = AVERROR_UNKNOWN;
            goto fail;
        }
    }
    D3D11_BOX box = { 0, 0, 0, (UINT)in->width, (UINT)in->height, 1 };
    ID3D11DeviceContext_CopySubresourceRegion(s->device_ctx, s->texture, 0, 0, 0, 0, in_tex, index, &box);

    out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
    if (!out) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    out_tex = (ID3D11Texture2D*)out->data[0];
    ID3D11Texture2D_GetDesc(out_tex, &desc);
    is_dst_nv12 = desc.Format == DXGI_FORMAT_NV12;

    D3D11_SHADER_RESOURCE_VIEW_DESC shader_resource_desc = { is_nv12 ? DXGI_FORMAT_R8G8_UNORM : DXGI_FORMAT_R16G16_UNORM, D3D11_SRV_DIMENSION_TEXTURE2D, {0, 1} };
    hr = ID3D11Device_CreateShaderResourceView(s->device, s->texture, &shader_resource_desc, &shader_resource_chroma);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "failed to CreateShaderResourceView error: 0x%x\n", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    D3D11_RENDER_TARGET_VIEW_DESC render_target_desc;
    render_target_desc.Format = is_dst_nv12 ? DXGI_FORMAT_R8G8_UNORM : DXGI_FORMAT_R16G16_UNORM;
    render_target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
    render_target_desc.Texture2D.MipSlice = 0;
    hr = ID3D11Device_CreateRenderTargetView(s->device, out_tex, &render_target_desc, &render_target_chroma);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "failed to CreateRenderTargetView error: 0x%x\n", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }
    
    d3d11scale_set_view_port(ctx, 1);
    ID3D11DeviceContext_IASetPrimitiveTopology(s->device_ctx,D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
    ID3D11DeviceContext_PSSetSamplers(s->device_ctx, 0, 1, &s->sampler_linear);
    ID3D11DeviceContext_OMSetRenderTargets(s->device_ctx, 1, &render_target_chroma, NULL);
    ID3D11DeviceContext_VSSetShader(s->device_ctx, s->vertex_shader, NULL, 0);
    ID3D11DeviceContext_PSSetShader(s->device_ctx, s->chroma_pixel_shader, NULL, 0);
    ID3D11DeviceContext_PSSetConstantBuffers(s->device_ctx, 0, 1, &s->chroma_cb_buffer);
    ID3D11DeviceContext_PSSetShaderResources(s->device_ctx, 0, 1, &shader_resource_chroma);
    ID3D11DeviceContext_Draw(s->device_ctx, 4, 0);

    shader_resource_desc.Format                     = is_nv12 ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_R16_UNORM;
    shader_resource_desc.ViewDimension              = D3D11_SRV_DIMENSION_TEXTURE2D;
    shader_resource_desc.Texture2D.MostDetailedMip  = 0;
    shader_resource_desc.Texture2D.MipLevels        = 1;
    hr = ID3D11Device_CreateShaderResourceView(s->device, s->texture, &shader_resource_desc, &shader_resource_luma);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "failed to CreateShaderResourceView error: 0x%x\n", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    render_target_desc.Format               = is_dst_nv12 ? DXGI_FORMAT_R8_UNORM : DXGI_FORMAT_R16_UNORM;
    render_target_desc.ViewDimension        = D3D11_RTV_DIMENSION_TEXTURE2D;
    render_target_desc.Texture2D.MipSlice   = 0;
    hr = ID3D11Device_CreateRenderTargetView(s->device, out_tex, &render_target_desc, &render_target_luma);
    if (FAILED(hr)) {
        av_log(ctx, AV_LOG_ERROR, "failed to CreateRenderTargetView error: 0x%x\n", hr);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    d3d11scale_set_view_port(ctx, 0);
    ID3D11DeviceContext_PSSetSamplers(s->device_ctx,        0, 1, &s->sampler_linear);
    ID3D11DeviceContext_OMSetRenderTargets(s->device_ctx,   1, &render_target_luma, NULL);
    ID3D11DeviceContext_VSSetShader(s->device_ctx,          s->vertex_shader, NULL, 0);
    ID3D11DeviceContext_PSSetShader(s->device_ctx,          s->luma_pixel_shader, NULL, 0);
    ID3D11DeviceContext_PSSetConstantBuffers(s->device_ctx, 0, 1, &s->luma_cb_buffer);
    ID3D11DeviceContext_PSSetShaderResources(s->device_ctx, 0, 1, &shader_resource_luma);
    ID3D11DeviceContext_Draw(s->device_ctx,                 4, 0);

    d3d11scale_query_finish(ctx);

    ret = av_frame_copy_props(out, in);
    if (ret < 0)
        goto fail;

    out->width = outlink->w;
    out->height = outlink->h;

    av_reduce(&out->sample_aspect_ratio.num, &out->sample_aspect_ratio.den,
        (int64_t)in->sample_aspect_ratio.num * outlink->h * link->w,
        (int64_t)in->sample_aspect_ratio.den * outlink->w * link->h,
        INT_MAX);

fail:
    D3D11_RELEASE(ShaderResourceView,   shader_resource_luma)
    D3D11_RELEASE(ShaderResourceView,   shader_resource_chroma)
    D3D11_RELEASE(RenderTargetView,     render_target_luma)
    D3D11_RELEASE(RenderTargetView,     render_target_chroma)

    av_frame_free(&in);
    if (ret >= 0) {
        return ff_filter_frame(outlink, out);
    }

    av_frame_free(&out);
    return ret;
}

#define OFFSET(x) offsetof(D3D11ScaleContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
static const AVOption options[] = {
    { "w",      "Output video width",  OFFSET(w_expr),     AV_OPT_TYPE_STRING, { .str = "iw"   }, .flags = FLAGS },
    { "h",      "Output video height", OFFSET(h_expr),     AV_OPT_TYPE_STRING, { .str = "ih"   }, .flags = FLAGS },
    { "format", "Output pixel format", OFFSET(format_str), AV_OPT_TYPE_STRING, { .str = "nv12" }, .flags = FLAGS },
    { "scale_type", "Output scale type", OFFSET(method_str), AV_OPT_TYPE_STRING, { .str = "binilear" }, .flags = FLAGS },
    { NULL },
};

static const AVClass d3d11scale_class = {
    .class_name = "scale_d3d11",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

static const AVFilterPad d3d11scale_inputs[] = {
    {
        .name         = "default",
        .type         = AVMEDIA_TYPE_VIDEO,
        .filter_frame = d3d11scale_filter_frame,
    },
};

static const AVFilterPad d3d11scale_outputs[] = {
    {
        .name         = "default",
        .type         = AVMEDIA_TYPE_VIDEO,
        .config_props = d3d11scale_config_props,
    },
};

const AVFilter ff_vf_scale_d3d11 = {
    .name = "scale_d3d11",
    .description = NULL_IF_CONFIG_SMALL("d3d11 video scaling and format conversion"),

    .init = d3d11scale_init,
    .uninit = d3d11scale_uninit,

    .priv_size = sizeof(D3D11ScaleContext),
    .priv_class = &d3d11scale_class,

    FILTER_INPUTS(d3d11scale_inputs),
    FILTER_OUTPUTS(d3d11scale_outputs),

    FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D11),

    .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
};
